package org.javahispano.javacup.tacticas_aceptadas.camigonz.org.icesoft.javacup;

import java.util.List;

import org.javahispano.javacup.modelo.Comando;
import org.javahispano.javacup.modelo.ComandoGolpearBalon;
import org.javahispano.javacup.modelo.Constantes;
import org.javahispano.javacup.modelo.Posicion;
import org.javahispano.javacup.modelo.SituacionPartido;
import org.javahispano.javacup.modelo.Tactica;

public class CTeamTactica extends CTeamBaseTactica implements Tactica {

	public List<Comando> ejecutar(SituacionPartido sp) {
		update(sp);
		alignPlayers();

		baseDefense();

		p[10].setState(9);
		if (myBall) {
			if (sp.puedenRematar().length > 0) {
				movePlayers();
				actionIfBall();
			} else {
				int j = 0;
				for (int i = 0; i < 11 && j < 2; i++) {
					if (closestPlayers[i] != 0 || sp.saco()) {
						if (j == 0) {
							p[closestPlayers[i]].setState(3);
						} else {
							p[closestPlayers[i]].setState(10);
						}
						j++;
					}
				}
			}
		} else {
			recovery();
			for (int i = 0; i < 11; i++) {
				if (closestPlayers[i] != 0 || sp.saco()) {
					p[closestPlayers[i]].setState(3);
					break;
				}
			}
		}

		if (ball.distancia(Constantes.centroArcoInf) <= 17.0) {
			p[0].setState(6);
		}

		if (sp.puedenRematar().length > 0) {
			actionIfShotDistance();
		}

		movePlayers();

		updateLast();
		return com;
	}

	private void movePlayers() {
		if (!myBall) {
			for (int i = 0; i < 11; i++) {
				if (players[i].distancia(ballPredictions[1]) < 0.6) {
					p[i].setState(3);
				}
			}
			p[closestPlayers[0]].setState(3);
		}
		for (int i = 0; i < 11; i++) {
			int s = p[i].getState();
			if (s == 0) {
				keeper();
			} else if (s == 1) {
				moveMap.put(i, p[i].getMovingTo());
			} else if (s == 2) {
				int idx = p[i].getFollowing();
				Posicion p1 = enemies[idx];
				double angle = p1.angulo(Constantes.centroArcoInf);
				double dist = ballEnemyDistance[idx];
				Posicion pf = p1.moverAngulo(angle, 3.0);

				angle = angle + (Math.PI / 2.0 - angle) * 2.0;
				if (dist > 25) {
					pf = p1.moverAngulo(angle, -1.6);
				} else if (dist > 10) {
					pf = p1.moverAngulo(angle, -0.7);
				} else if (players[i].getY() < ball.getY()) {
					if (playerSteps[i] < 0) {
						pf = ball;
					} else {
						pf = ballPredictions[playerSteps[i]];
					}
				} else if (p1.getY() < -40.0) {
					angle = p1.angulo(players[0]);
					pf = p1.moverAngulo(angle, 0.7);
				} else {
					angle = ball.angulo(Constantes.centroArcoInf);
					double _5 = 5.0 * Math.PI / 180.0;
					pf = ball.moverAngulo(angle + _5, 1.0);
				}

				if (ballPlayerDistance[i] <= 2.4) {
					pf = ballPredictions[1];
				}
				double d2 = enemies[idx].distancia(Constantes.centroArcoInf);
				if (d2 < 2.0 && dist > 10.0) {
					pf = p1.moverAngulo(enemies[idx].angulo(ball), 0.6);
				}
				moveMap.put(i, pf);
			} else if (s == 3) {
				int s1 = playerSteps[i] < 0 ? 0 : playerSteps[i];
				if (s1 > 20) {
					s1 = s1 / 2;
				}
				moveMap.put(i, ballPredictions[s1].setDentroCampoJuego());
			} else if (s == 4) {
				int idx = p[i].getFollowing();
				double angle = enemies[idx].angulo(ball);
				moveMap.put(i, enemies[idx].moverAngulo(angle, 0.7));
			} else if (s == 5) {
				double angle = ball.angulo(Constantes.centroArcoInf);
				double _5 = 5.0 * Math.PI / 180.0;
				moveMap.put(i, ball.moverAngulo(angle + _5, 1.0));
			} else if (s == 6) {
				keeper();
				Posicion p1 = moveMap.get(i);
				double d1 = ball.distancia(Constantes.centroArcoInf);
				d1 = Math.min(d1, ball.distancia(Constantes.posteIzqArcoInf));
				d1 = Math.min(d1, ball.distancia(Constantes.posteDerArcoInf));
				double it1 = Math.floor(d1 / Constantes.REMATE_VELOCIDAD_MAX);
				if (d1 <= 6.0) {
					int st = playerSteps[i] > 7 ? 2 : playerSteps[i];
					moveMap.put(i, ballPredictions[st].setDentroCampoJuego());
				} else {
					double a = p1.angulo(ballPredictions[1]);
					double sp = Constantes.getVelocidad(playerDetails[i].getVelocidad());
					moveMap.put(i, p1.moverAngulo(a, it1 * sp));
				}
			} else if (s == 7) {
				Posicion p1 = p[i].getAlign();
				double d = ball.distancia(p1);
				if (d > 35.0) {
					p1 = ball.moverAngulo(ball.angulo(p1), 35.0);
				}
				moveMap.put(i, p1);
			} else if (s == 8) {
				double d = ballPlayerDistance[i];
				if (d < 2.0) {
					moveMap.put(i, ballPredictions[1]);
				} else {
					moveMap.put(i, predictBall(playerSteps[i]));
				}
			} else if (s == 9) {
				Posicion p1 = Constantes.centroArcoSup;
				double a = p1.angulo(ball);
				moveMap.put(i, p1.moverAngulo(a, 12.0));
			} else if (s == 10) {
				int idx = p[i].getFollowing();
				Posicion p1 = enemies[idx];
				double a = p1.angulo(ball);
				double d = ballEnemyDistance[idx];
				Posicion pf = ball.moverAngulo(a, d + 1.2);

				moveMap.put(i, pf);
			}
		}
	}

	private void sendAway(int idx) {
		final double _a = 20.0 * Math.PI / 180.0;
		double a = ball.angulo(Constantes.centroArcoSup);
		boolean s = true;
		for (int i = 0; i < 11; i++) {
			double d = ballEnemyDistance[i];
			if ((d < 3.1 && angleDiference(a, ball.angulo(enemies[i]), _a)) || d < 1.4) {
				s = false;
				break;
			}
		}
		if (s) {
			com.add(new ComandoGolpearBalon(idx, Constantes.centroArcoSup, 1.0, true));
			return;
		}

		double sum = -Math.PI / 2.0;
		double cnt = 1.0;
		for (int i = 0; i < 11; i++) {
			double d = ballEnemyDistance[i];
			if (d < 3.1) {
				a = ball.angulo(enemies[i]);
				sum += a;
				cnt++;
			}
		}

		double angle = sum / cnt;
		angle = angle + Math.PI;
		if (angle >= Math.PI) {
			angle = angle - 2.0 * Math.PI;
		}

		if (angle < 0.0 && angle >= -Math.PI) {
			if (ball.getX() < 0.0) {
				angle = 170.0 * 180.0 / Math.PI;
			} else {
				angle = 10.0 * 180.0 / Math.PI;
			}
		}

		angle = angle * 180.0 / Math.PI;
		com.add(new ComandoGolpearBalon(idx, angle, 1.0, true));
	}

	private boolean move(boolean front, double minDist) {
		int idx = sp.puedenRematar()[0];

		double a = ball.angulo(Constantes.centroArcoSup);
		double delta = Constantes.getErrorAngular(playerDetails[idx].getPresicion());

		double selAngle = 0.0;
		boolean found = false;
		int it = (int) Math.floor(Math.PI / delta);
		double closest = 0.0;
		for (int i = 0; i < it; i++) {
			for (double j = -1.0; j <= 1.2; j += 2.0) {
				double angle = a + j * i * delta;
				if (front && angle < 0.0 && angle > -Math.PI) {
					continue;
				}

				Posicion p1 = ball.moverAngulo(angle, minDist + 0.6);

				if (checkClear(ball, p1) >= 0) {
					continue;
				}

				double cl = 1000.0;
				for (int k = 0; k < 11; k++) {
					if (ballEnemyDistance[k] < minDist && dot(p1, ball, enemies[k]) <= 0) {
						double d1 = linePointDistance(ball, p1, enemies[k], true);
						if (d1 < cl) {
							cl = d1;
						}
					}
				}

				if (cl > closest) {
					closest = cl;
					found = true;
					selAngle = angle;
				}
			}
		}

		if (found) {
			p[idx].setState(1);
			p[idx].setMovingTo(players[idx].moverAngulo(selAngle, minDist));
			com.add(new ComandoGolpearBalon(idx));
		}
		return found;
	}

	private void makeLowPass(int idx, int destIdx) {
		Posicion b = players[destIdx];
		double d = ballPlayerDistance[destIdx];

		double st = Constantes.getVelocidadRemate(playerDetails[idx].getRemate());
		double s = 2.0 * Constantes.FACTOR_DISMINUCION_VEL_BALON_SUELO;
		s = Math.sqrt(s * d);
		double s2 = Math.min(1.0, s / st);
		if (d < 7.0 && s2 == 1.0) {
			s2 = 0.75;
		}
		if (destIdx != 0)
			p[destIdx].setState(8);
		com.add(new ComandoGolpearBalon(idx, b.moverPosicion(0.0, 0.6), s2, false));
	}

	private boolean highShot(int idx, Posicion dest, double desiredHeight) {
		double st = Constantes.getVelocidadRemate(playerDetails[idx].getRemate());

		double y = desiredHeight;
		double x = ball.distancia(dest);
		double x1 = x + 6.0;
		double g = Constantes.G;

		double angle = Math.atan(4.0 * y / x1);
		double speed = Math.sqrt(2 * g * y) / Math.sin(angle);

		double normSpeed = Math.min(speed / st, 1.0);
		double normAngle = Math.min(angle * 180.0 / Math.PI, 60.0);
		com.add(new ComandoGolpearBalon(idx, dest, normSpeed, normAngle));
		return true;
	}

	private void recovery() {
		if (sp.sacaRival()) {
			for (int i = defensePlayers + 1; i < 11; i++) {
				p[i].setState(7);
			}
			return;
		}

		int defP[] = new int[] { defensePlayers + 1, defensePlayers + 2, defensePlayers + 3 };
		followEnemies(defensePlayers, defensePlayers + defP.length - 1, defP);

		// Fix so not following very far away players
		int cnt = 0;
		for (int i = 0; i <= defP.length - 1; i++) {
			Posicion ep = enemies[closestEnemiesGoal[defensePlayers + i]];
			if (ep.getY() >= 0.0) {
				p[_solution[i]].setState(10);
				p[_solution[i]].setFollowing(closestEnemiesGoal[cnt++]);
				// if (enemies[closestEnemiesGoal[cnt]].getY() >= 0.0) {
				// p[_solution[i]].setState(2);
				// }
			}
		}

		int idx = -1;
		double min = 10000;
		int maxP = defensePlayers + 4;
		for (int i = defensePlayers + 1; i < maxP; i++) {
			if (players[i].getY() < ballPredictions[1].getY()) {
				double d = Math.abs(players[i].getX() - ballPredictions[1].getX());
				if (d < min) {
					idx = i;
					min = d;
				}
			}
		}
		if (idx != -1) {
			p[idx].setState(10);
		}
	}

	private void actionIfBall() {
		int idx = sp.puedenRematar()[0];
		analyzePass();

		boolean t = false;
		int sel = -1;
		double cl = -1.0;
		for (int i = 10; i >= 6; i--) {
			if (p[i].getState() == 8) {
				p[i].stopWaitPass();
			}
			if (idx != i && passSolution[i] < 10
					&& (players[i].getY() - 2.0 >= players[idx].getY() || goalPossibility[i])) {
				if (distanceEnemyP[i] > cl) {
					cl = distanceEnemyP[i];
					sel = i;
					t = true;
				}
			}
		}

		if (t) {
			pass(idx, sel);
		}

		if (!t) {
			double minD = 5.0;
			if (ball.getY() >= 0.0) {
				minD = 3.0;
			}
			if (!move(true, 7.0)) {
				if (ball.getY() > 35 && !move(true, 3.0)) {
					if (!selectPass(true, true, minD)) {
						if (!selectPass(false, false, 0.0)) {
							Posicion p1 = Constantes.centroArcoSup;
							com.add(new ComandoGolpearBalon(idx, p1, 1.0, 30));
						}
					}
				}
			}
		}
	}

	private boolean selectPass(boolean front, boolean clear, double minDist) {
		int idx = sp.puedenRematar()[0];
		double y = players[idx].getY();

		int s1 = -1;
		int s2 = -1;
		double closer = -1.0;
		double closer2 = -1.0;
		for (int i = 5; i < 11; i++) {
			if (i != idx) {
				if (!front || y < players[i].getY() - 1.0) {
					if (!clear || (passSolution[i] < 6 && distanceEnemyP[i] >= minDist)) {
						if (goalPossibility[i] && passSolution[i] < 8
								&& distanceEnemyP[i] > closer2) {
							closer2 = distanceEnemyP[i];
							s1 = i;
						}
						if (distanceEnemyP[i] > closer && passSolution[i] < 6) {
							closer = distanceEnemyP[i];
							s2 = i;
						}
					}
				}
			}
		}

		int sel = s1;
		String t = "-";
		if (s1 == -1) {
			t += "-";
			sel = s2;
		}

		if (sel > -1) {
			pass(idx, sel);
			return true;
		}

		return false;
	}

	private void pass(int idx, int sel) {
		int passTo = sel;
		while (passMemory[passTo] != idx) {
			passTo = passMemory[passTo];
		}

		if (passCost[idx][passTo] < 5) {
			makeLowPass(idx, passTo);
		} else if (passCost[idx][passTo] == 5) {
			p[passTo].setState(8);
			if (enemyInLOP[passTo] != -1) {
				double a = enemies[enemyInLOP[passTo]].angulo(players[passTo]);
				Posicion e = players[passTo];
				e.moverAngulo(a, 1.2);
				highShot(idx, e, 3.0);
			} else {
				highShot(idx, players[passTo].moverPosicion(0.0, 1.2), 3.0);
			}
		} else {
			highShot(idx, players[passTo].moverPosicion(0.0, 1.2), 3.0);
		}
	}

	private boolean shootWithCheck(int idx) {
		double dist = ball.distancia(Constantes.centroArcoSup);
		if (dist <= 19.0 && (goalPossibility[idx] || dist <= 10.0)) {
			shoot(idx);
			return true;
		}
		return false;
	}

	private void shoot(int idx) {
		final Posicion pd = Constantes.posteDerArcoSup;
		final Posicion pi = Constantes.posteIzqArcoSup;
		double delta = Constantes.getErrorAngular(playerDetails[idx].getPresicion());
		delta = (delta - delta / 2.0) * 1.2;
		Posicion des = Constantes.centroArcoSup;
		double d = ball.distancia(des);
		Posicion k = enemies[enemyKeeper];
		double offset = k.getY() - des.getY();
		double d1 = linePointDistance(ball, pi, k, true);
		double d2 = linePointDistance(ball, pd, k, true);
		if (d1 < d2) {
			double a = ball.angulo(pd);
			a = a + delta;
			des = new Posicion(ball.moverAngulo(a, d * 1.2));
		} else {
			double a = ball.angulo(pi);
			a = a - delta;
			des = new Posicion(ball.moverAngulo(a, d * 1.2));
		}
		if (offset >= 4.8) {
			highShot(idx, des, 3.2);
		} else {
			com.add(new ComandoGolpearBalon(idx, des, 1.0, 0.0));
		}
	}

	private void actionIfShotDistance() {
		int idx = sp.puedenRematar()[0];
		int dp = Math.min(defensePlayers, 4);
		if ((idx <= dp || ball.getY() < -25.0) && !sp.saco()) {
			if (closeEnemies[idx] >= 1 || idx == 0 || ball.getY() < -30.0) {
				sendAway(idx);
				for (int i = 1; i < 11; i++) {
					p[i].stopWaitPass(3);
				}
			}
			return;
		} else if (idx <= dp) {
			com.add(new ComandoGolpearBalon(idx, Constantes.centroArcoSup, 1.0, 45));
			return;
		}
		shootWithCheck(idx);

		if (sp.saco() && ball.getY() > 25.0) {
			com.add(new ComandoGolpearBalon(idx, Constantes.penalSup, 1.0, 45));
		} else if (sp.saco()) {
			com.add(new ComandoGolpearBalon(idx, Constantes.centroArcoSup, 1.0, 45));
		}
	}

	private void baseDefense() {
		p[0].setState(0);

		int defP[] = new int[] { 1, 2, 3, 4 };
		defensePlayers = 4;

		followEnemies(0, defP.length - 1, defP);

		// Fix so not following very far away players
		int cnt = 0;
		for (int i = 0; i <= defP.length - 1; i++) {
			Posicion ep = enemies[closestEnemiesGoal[i]];
			if (ep.getY() >= 0.0) {
				p[_solution[i]].setState(2);
				p[_solution[i]].setFollowing(closestEnemiesGoal[cnt++]);
				if (enemies[closestEnemiesGoal[cnt]].getY() >= -5.0) {
					cnt--;
				}
			}
		}

		cnt = 5;
		for (int i = defensePlayers; i < 11 && cnt < 7; i++) {
			if (enemies[closestEnemiesGoal[i]].getY() <= 0.0) {
				p[cnt].setState(2);
				p[cnt].setFollowing(closestEnemiesGoal[i]);
				cnt++;
				defensePlayers++;
			}
		}
	}

	private void followEnemies(int lower, int upper, int players[]) {
		_used = new boolean[players.length];
		_players = players;
		_solution = new int[upper - lower + 1];
		_currentSolution = new int[upper - lower + 1];
		_minDist = 200000.0;
		_upper = upper;
		_lower = lower;
		followEnemiesRec(lower, 0.0);

		for (int i = 0; i <= upper - lower; i++) {
			p[_solution[i]].setState(2);
			p[_solution[i]].setFollowing(closestEnemiesGoal[i + lower]);
		}

	}

	/* Variables for recursive function */
	private boolean _used[];
	private int _players[];
	private int _upper;
	private int _lower;
	private int[] _currentSolution;
	private double _minDist;
	private int[] _solution;

	private void followEnemiesRec(int level, double dist) {
		if (level > _upper) {
			if (dist < _minDist) {
				for (int i = 0; i < _solution.length; i++)
					_solution[i] = _currentSolution[i];
				_minDist = dist;
			}
			return;
		}
		for (int i = 0; i < _players.length; i++) {
			if (!_used[i]) {
				_used[i] = true;
				_currentSolution[level - _lower] = _players[i];
				double d = enemies[closestEnemiesGoal[level]].distancia(players[_players[i]]);
				followEnemiesRec(level + 1, dist + d);
				_used[i] = false;
			}
		}
	}

}